using BepuUtilities;
using BepuUtilities.Collections;
using BepuUtilities.Memory;
using OpenTK;
using OpenTK.Input;
using System;
using System.Runtime.CompilerServices;

namespace DemoUtilities
{
    using KeySet = QuickSet<Key, KeyComparer>;
    using MouseButtonSet = QuickSet<MouseButton, MouseButtonComparer>;
    struct KeyComparer : IEqualityComparerRef<Key>
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool Equals(ref Key a, ref Key b)
        {
            return a == b;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int Hash(ref Key item)
        {
            return (int)item;
        }
    }
    struct MouseButtonComparer : IEqualityComparerRef<MouseButton>
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool Equals(ref MouseButton a, ref MouseButton b)
        {
            return a == b;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int Hash(ref MouseButton item)
        {
            return (int)item;
        }
    }
    public class Input : IDisposable
    {
        NativeWindow window;


        // 您可以使用类似GetState的东西来避免显式获取这些内容,但请耸耸肩。这使事件仅局限于窗口,我们可以自己进行一些逻辑操作。
        KeySet anyDownedKeys;
        KeySet downedKeys;
        KeySet previousDownedKeys;
        MouseButtonSet anyDownedButtons;
        MouseButtonSet downedButtons;
        MouseButtonSet previousDownedButtons;
        BufferPool pool;
        public QuickList<char> TypedCharacters;

        /// <summary>
        /// 通过在每次刷新时将鼠标重新居中,强制鼠标停留在屏幕中心。
        /// </summary>
        public bool MouseLocked
        {
            get; set;
        }
        Int2 WindowCenter { get { return new Int2(window.Width / 2, window.Height / 2); } }

        /// <summary>
        /// 获取或设置窗口坐标中的鼠标位置,而不更改净鼠标增量。
        /// </summary>
        public Int2 MousePosition
        {
            get
            {
                var state = Mouse.GetCursorState();
                var clientPosition = window.PointToClient(new Point(state.X, state.Y));
                return new Int2 { X = clientPosition.X, Y = clientPosition.Y };
            }
            set
            {
                // 请注意,更改光标位置不会更改原始鼠标x/y。
                var screen = window.PointToScreen(new Point(value.X, value.Y));
                Mouse.SetPosition(screen.X, screen.Y);
            }
        }

        /// <summary>
        /// 获取自上次刷新以来鼠标位置的更改。
        /// </summary>
        public Int2 MouseDelta
        {
            get
            {
                return mouseDelta;
            }
        }

        /// <summary>
        /// 获取自上次刷新以来向上滚动鼠标滚轮的量,而不管向下滚动量有多大。
        /// </summary>
        public float ScrolledUp { get; private set; }
        /// <summary>
        /// 获取自上次刷新以来鼠标滚轮向下滚动量,而不管向上滚动量有多大。
        /// </summary>
        public float ScrolledDown { get; private set; }
        /// <summary>
        /// 获取自上次刷新以来的鼠标滚轮滚动增量。
        /// </summary>
        public float ScrollDelta { get { return ScrolledUp + ScrolledDown; } }

        public Input(Window window, BufferPool pool)
        {
            this.window = window.window;
            this.window.KeyDown += KeyDown;
            this.window.KeyUp += KeyUp;
            this.window.MouseDown += MouseDown;
            this.window.MouseUp += MouseUp;
            this.window.MouseWheel += MouseWheel;
            this.window.KeyPress += KeyPress;
            this.pool = pool;
            anyDownedButtons = new MouseButtonSet(8, pool);
            downedButtons = new MouseButtonSet(8, pool);
            previousDownedButtons = new MouseButtonSet(8, pool);
            anyDownedKeys = new KeySet(8, pool);
            downedKeys = new KeySet(8, pool);
            previousDownedKeys = new KeySet(8, pool);
            TypedCharacters = new QuickList<char>(32, pool);
        }

        private void KeyPress(object sender, KeyPressEventArgs e)
        {
            TypedCharacters.Add(e.KeyChar, pool);
        }

        private void MouseWheel(object sender, MouseWheelEventArgs e)
        {
            if (e.DeltaPrecise > 0)
                ScrolledUp += e.DeltaPrecise;
            else
                ScrolledDown += e.DeltaPrecise;
        }

        private void MouseDown(object sender, MouseButtonEventArgs e)
        {
            anyDownedButtons.Add(e.Button, pool);
            downedButtons.Add(e.Button, pool);
        }
        private void MouseUp(object sender, MouseButtonEventArgs e)
        {
            downedButtons.FastRemove(e.Button);
        }

        private void KeyDown(object sender, KeyboardKeyEventArgs e)
        {
            anyDownedKeys.Add(e.Key, pool);
            downedKeys.Add(e.Key, pool);
            // 不幸的是,退格键不是按键报告的,所以我们手动操作。
            if (e.Key == Key.BackSpace)
                TypedCharacters.Add('\b', pool);
        }
        private void KeyUp(object sender, KeyboardKeyEventArgs e)
        {
            downedKeys.FastRemove(e.Key);
        }



        /// <summary>
        /// 获取当前是否根据最新的事件处理调用按键。
        /// </summary>
        /// <param name="key">要检查的键。</param>
        /// <return>如果键是在最近的事件处理调用中按下的,则为True,否则为False。</return>
        public bool IsDown(Key key)
        {
            return downedKeys.Contains(key);
        }

        /// <summary>
        /// 获取键在上一次刷新时是否已按下。
        /// </summary>
        /// <param name="key">要检查的键。</param>
        /// <return>如果键在上一次刷新时按下,则为True,否则为False。</return>
        public bool WasDown(Key key)
        {
            return previousDownedKeys.Contains(key);
        }

        /// <summary>
        /// 获取在上一次刷新和直到最后一次事件过程调用之间的任何点是否发生了Down事件,以获取在上一次刷新中未按下的键。
        /// </summary>
        /// <param name="key">要检查的键。</param>
        /// <return>如果键是在最近的事件处理调用中按下的,则为True,否则为False。</return>
        public bool WasPushed(Key key)
        {
            return !previousDownedKeys.Contains(key) && anyDownedKeys.Contains(key);
        }


        /// <summary>
        /// 根据最新的事件处理调用获取当前是否按下了按钮。
        /// </summary>
        /// <param name="Button">要选中的按钮。</param>
        /// <return>如果按钮是在最近的事件处理调用中按下的,则为True,否则为False。</return>
        public bool IsDown(MouseButton button)
        {
            return downedButtons.Contains(button);
        }

        /// <summary>
        /// 获取上一次刷新时按钮是否按下。
        /// </summary>
        /// <param name="MouseButton">要检查的按钮。</param>
        /// <return>如果按钮在上一次刷新时按下,则为True,否则为False。</return>
        public bool WasDown(MouseButton mouseButton)
        {
            return previousDownedButtons.Contains(mouseButton);
        }

        /// <summary>
        /// 获取在上一次刷新到上一次刷新中未按下的按钮的最后一次事件进程调用之间的任意点是否发生了按下事件。
        /// </summary>
        /// <param name="Button">要选中的按钮。</param>
        /// <return>如果按钮是在最近的事件处理调用中按下的,则为True,否则为False。</return>
        public bool WasPushed(MouseButton button)
        {
            return !previousDownedButtons.Contains(button) && anyDownedButtons.Contains(button);
        }

        Int2 mouseDelta;
        Int2 previousRawMouse;
        public void Start()
        {
            var currentState = Mouse.GetState();
            // 如果时间足够长,理论上这可能会造成溢出。
            // 但是,使用高DPI鼠标需要花费数小时的时间,而这是一个演示应用程序……
            mouseDelta.X = currentState.X - previousRawMouse.X;
            mouseDelta.Y = currentState.Y - previousRawMouse.Y;
            previousRawMouse = new Int2(currentState.X, currentState.Y);
            if (MouseLocked)
            {
                // 这是相当愚蠢的,但它工作得相当好,而且我们不能很容易地访问窗口-通过opentk提供的捕获内容(我知道吗？)。
                // 如果重要的话可以稍后更改,但实际上这无关紧要。
                MousePosition = WindowCenter;
                window.CursorVisible = false;
            }
            else
            {
                window.CursorVisible = true;
            }

        }
        public void End()
        {
            anyDownedKeys.Clear();
            anyDownedButtons.Clear();
            previousDownedKeys.Clear();
            previousDownedButtons.Clear();
            for (int i = 0; i < downedKeys.Count; ++i)
                previousDownedKeys.Add(downedKeys[i], pool);
            for (int i = 0; i < downedButtons.Count; ++i)
                previousDownedButtons.Add(downedButtons[i], pool);
            ScrolledDown = 0;
            ScrolledUp = 0;
            TypedCharacters.Count = 0;
        }

        /// <summary>
        /// 从窗口解除输入管理的挂钩。
        /// </summary>
        public void Dispose()
        {
            window.KeyDown -= KeyDown;
            window.KeyUp -= KeyUp;
            window.MouseDown -= MouseDown;
            window.MouseUp -= MouseUp;

            anyDownedKeys.Dispose(pool);
            downedKeys.Dispose(pool);
            previousDownedKeys.Dispose(pool);
            anyDownedButtons.Dispose(pool);
            downedButtons.Dispose(pool);
            previousDownedButtons.Dispose(pool);
        }
    }
}
